import { Injectable } from '@nestjs/common';
import { Observable, Subject } from 'rxjs';
import { z } from "zod";
import { Ollama, OllamaEmbeddings, ChatOllama } from "@langchain/ollama";
import { Document } from "@langchain/core/documents";

import { FaissStore } from '@langchain/community/vectorstores/faiss';
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
import { PDFLoader } from '@langchain/community/document_loaders/fs/pdf'; // 导入 PDFLoader 类，用于加载 PDF 文件。

import { StringOutputParser } from '@langchain/core/output_parsers'; // 导入 StringOutputParser 类，用于解析输出字符串。  
import { ChatPromptTemplate } from "@langchain/core/prompts"; // 导入 ChatPromptTemplate 类，用于构建对话模板。
import { RunnableSequence } from '@langchain/core/runnables';

import { getWeather } from '@/tools/getCurrentWeather'; // 导入 getCurrentWeather 函数，用于获取当前天气。

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
// 引入 StdioClientTransport 类
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
import { McpServerService } from "nest-mcp-sse";
import { loadMcpTools } from "@langchain/mcp-adapters";

@Injectable()
export class DeepseekService {

  private model: Ollama;

  private chatModel: ChatOllama;

  private embeddingModel: OllamaEmbeddings;

  private messageSubject = new Subject<MessageEvent>();

  private outputParser = new StringOutputParser();

  private mcpClient: Client;

  constructor(private mcpServerService: McpServerService) {
    try {
      this.model = new Ollama({
        baseUrl: 'http://127.0.0.1:11434', // ollama服务的地址
        model: 'deepseek-r1:latest', // 通过ollama部署的模型名称
      });

      this.embeddingModel = new OllamaEmbeddings({ // 初始化 embedding 模型，用于向量化文本，用于后续的检索。

        baseUrl: 'http://127.0.0.1:11434', // ollama服务的地址

        model: 'nomic-embed-text:latest', // 通过ollama部署的模型名称
      })

      this.chatModel = new ChatOllama({
        // temperature: 0.6,
        temperature: 0,
        baseUrl: 'http://127.0.0.1:11434', // ollama服务的地址
        model: 'qwen2.5:3b', // 通过ollama部署的模型名称
        // model: 'ishumilin/deepseek-r1-coder-tools:1.5b', // 通过ollama部署的模型名称
      })

      // 初始化mcp服务器
      this.mcpServerService.getServer('mcp-server')?.tool(
        'get_weather_info',
        '根据城市名获取天气信息',
        {
          city: z.string()
        },
        getWeather
      );

      this.connectClient();

    } catch (err) {
      console.log('nonono', err)
    }
  }

  async connectClient() {
    try {
      this.mcpClient = new Client({
        name: "mcp-server",
        version: "1.0.0",
      });
      const transport = new SSEClientTransport(new URL('http://127.0.0.1:9528/deepseek/mcp-server/sse'))

      await this.mcpClient.connect(transport);
      console.log(102, 'mcpClient connect success');
    } catch (err) {
      console.log('connectClient', err)
    }
  }

  async tools(question) {
    try {

      const tools = await loadMcpTools("math", this.mcpClient, {
        // Whether to throw errors if a tool fails to load (optional, default: true)
        throwOnLoadError: true,
        // Whether to prefix tool names with the server name (optional, default: false)
        prefixToolNameWithServerName: false,
        // Optional additional prefix for tool names (optional, default: "")
        additionalToolNamePrefix: "",
      });
      const tool = this.chatModel.bindTools(tools)

      const prompt = ChatPromptTemplate.fromMessages([
        ['system', '你是一个擅长回答问题的助手，你可以根据用户的问题，回答用户的问题。除了正常问答外，还可以帮用户查询天气信息'],
        ['user', '{question}'],
      ]);

      const chain = prompt.pipe(tool);

      const res = await chain.invoke({ question });
      const tool_calls = res.tool_calls;
      const content = res.content;

      if (tool_calls.length) {
        //  调用函数，获取当地天气信息。然后将结果与用户的问题一起返回给大模型，重新润色回答。

        const data: any = await this.mcpClient?.callTool?.({
          name: tool_calls[0].name,
          arguments: tool_calls[0].args,
        });

        //  Todo：可以通过ai结合问题再进行下一轮的对话，来获取更准确的答案。

        return this.addList(data.content[0].text);
      }

      return this.addList(content);
    } catch (err) {
      console.log(40, err)
    }
  }


  async addList(stream) {
    let res = '';
    for await (const str of stream) {
      this.messageSubject.next({
        data: JSON.stringify({ answer: str, end: false }),
      } as MessageEvent);
      res += str;
    }
    this.messageSubject.next({
      data: JSON.stringify({ answer: '', end: true }),
    } as MessageEvent);

    return res;
  }
  async answer(question) {
    try {
      const prompt = ChatPromptTemplate.fromTemplate('请尽你所能回答一下问题: \n {question}');

      const chain = prompt.pipe(this.chatModel).pipe(this.outputParser);

      const stream = await chain.stream({ question });

      return await this.addList(stream);
    } catch (err) {
      console.log(40, err)
    }
  }

  async addRagData(url: string) {
    try {
      const loader = new PDFLoader('./src/assets/data/中华人民共和国民法典.pdf');
      const docs = await loader.load(); // 加载 PDF 文件。返回一个 Document 对象数组。Document 对象包含了 PDF 文件的文本内容和元数据。

      const splitter = new RecursiveCharacterTextSplitter({
        chunkSize: 8000,
        chunkOverlap: 1000
      });

      const splitDocs = await splitter.splitDocuments(docs); // 对 Document 对象数组进行拆分，将 Document 对象拆分为多个 Document 对象。拆分的方式是根据 chunkSize 和 chunkOverlap 进行拆分。

      const vectorStore = await FaissStore.fromDocuments(splitDocs, this.embeddingModel); // 向量化 Document 对象数组。返回一个 VectorStore 对象数组。VectorStore 对象包含了 Document 对象的向量化结果和元数据。

      const directory = './src/db/minfadian-more.index';
      const res = await vectorStore.save(directory); // 保存 VectorStore 对象数组到本地。

      return res;
    } catch (err) {
      console.log('err', err)
      return false;
    }
  }

  async rag(question) {
    const SYSTEM_TEMPLATE = `
    你是一个熟读民法典的法律顾问，精通根据民法典详细解释和回答问题，你在回答时会引用民法典。
    并且回答时仅根据原文，尽可能回答用户问题，如果原文中没有相关内容，直接回答“原文中没有相关内容”，如果有的话，引用原文片段进行回答。
    以下是原文中跟用户回答相关的内容：
    {context}
    现在，你需要基于原文，回答以下问题：
    {question}
  `;
    const prompt = ChatPromptTemplate.fromTemplate(SYSTEM_TEMPLATE); // 构建对话模板，用于后续的对话。

    const directory = './src/db/minfadian-more.index';
    const vectorStore = await FaissStore.load(directory, this.embeddingModel); // 加载 VectorStore 对象数组。返回一个 VectorStore 对象数组。VectorStore 对象包含了 Document 对象的向量化结果和元数据。

    const retriever = vectorStore.asRetriever(2);

    const convertDocsToString = (documents: Document[]): string => {
      const res = documents.map((document) => document.pageContent).join("\n");
      return res;
    }

    const contextRetrieverChain = RunnableSequence.from([
      (input) => input.question,
      retriever,
      convertDocsToString,
    ])

    const ragChain = RunnableSequence.from([
      {
        context: contextRetrieverChain,
        question: (input) => input.question,
      },
      prompt, // 对话模板。用于后续的对话。
      this.model, // 模型。用于后续的对话。
      new StringOutputParser(), // 输出解析器。用于后续的对话。
    ])

    const stream = await ragChain.stream({ question }); // 执行对话。返回一个 Observable 对象。Observable 对象包含了对话的结果。

    return await this.addList(stream);

  }

  stream(): Observable<MessageEvent> {
    return this.messageSubject.asObservable();
  }
}
